<?php
/**************************************************************************
 * Naanal PHP Framework, Simple, Efficient and Developer Friendly
 * Ver 3.0, Copyright (C) <2010>  <Tamil Amuthan. R>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ************************************************************************/

///this class is created assuming the Named Placeholders of prepared statement is used. index based parameters in the sql statement will not work
class ClsNaanalPDO extends PDO
{
    protected $statement=null; 
    public $error=null;
    public $arrLastSql=array();
    private $dbname="";
    public $name="";
    public $sql="";
    public $arrParameter=array();
    
    protected $arrError=array();
    protected $stopOnError=false;
    
    public static $instanceCount=0;
    public static $pdo=array();
    public static $instanceMax=10;
    public static $arrStaticError=array();
    ///temporary row holder when using query_result method
    public static $arrFetchRow=array();
    private $rowCount=0;

    /**
    * possible sqlserver values are mysql, sybase, mssql
    */
    function __construct($host,$user,$pass,$dbname=null,$sqlserver="mysql") 
    {
        try 
        {
            if(strtolower($sqlserver)=="mysql")
            {
                $dsn="mysql:host=$host";
                if($dbname)
                {
                    $this->dbname=$dbname;
                    $dsn="mysql:host=$host;dbname=$dbname";
                }
                parent::__construct($dsn, $user, $pass);
            }
            else if(strtolower($sqlserver)=="sqlite")
            {
                $dsn="sqlite:{$dbname}";
                parent::__construct($dsn);
            }
            else
            {
                die ("Unknown DSN. Please set config.php");
            }
            addLog("dsn is {$dsn}");
            $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $this->setStopOnError();
        }   
        catch(PDOException $e)
        {
            die($e); 
        }
    }
    public function resetFetchRow()
    {
        self::$arrFetchRow=array();
    }
    public function query_result(&$PDOStatement,$row,$column)
    {
        if(!isset(self::$arrFetchRow[$row]))
        {
            self::$arrFetchRow[]=$PDOStatement->fetch();
        }
        if(!isset(self::$arrFetchRow[$row]))
        {
            die("Unexpected result");
        }
        return self::$arrFetchRow[$row][$column];
    }
    public function &query($query)
    {
        self::$arrFetchRow=array();
        try
        {
            $this->statement=parent::query($query);
        }
        catch(Exception $e)
        {
            trace("PDO Query execution failed prematurely. Error: ".print_r($e));
        }
        //if($this->statement===false) return false;
        if($this->statement===false)
        {
            return $this->statement;
        }
        $this->rowCount=$this->statement->rowCount();
        return $this->statement;
    }
    public function &pquery($query,$param)
    {
        self::$arrFetchRow=array();
        $arrSplit=preg_split('/([\s]?[\?]+[\s]?)/', $query);
        if(count($arrSplit)>1)
        {
            $this->statement=$this->prepare($query);
            if($this->statement===false) return false;
            $success=$this->statement->execute($param);
            if($success===false) return false;
            $this->rowCount=$this->statement->rowCount();
            return $this->statement;
        }
        else
        {
            $this->statement=$this->query($query);
            if($this->statement===false) return false;
            $this->rowCount=$this->statement->rowCount();
            return $this->statement;
        }
    }
    public function fetch_array()
    {
        return $this->statement->fetch();
    }
    public function fetch_row()
    {
        return $this->statement->fetch(PDO::FETCH_NUM);
    }
    public function fetch_assoc()
    {
        return $this->statement->fetch(PDO::FETCH_ASSOC);
    }
    public function num_rows()
    {
        return $this->rowCount;
    }
    function setStopOnError()
    {
        $this->stopOnError=true;
    }
    function getDatabase()
    {
        return $this->dbname;
    }
    function __destruct()
    {
        if(!is_null($this->statement)) $this->statement=null;
    }
    public static function &getInstance($isDynamicDatabase=false)
    {
        if($isDynamicDatabase)
        {
            if($db=getDynamicDatabase())
                return self::getNamedInstance($db);
            else
            {
                $ret=false;
                return $ret;
            }
        }
        else
        {
            return self::getNamedInstance(getDefaultDatabase());
        }
    }
    
    /**
    * if $dbname is false, the pdo will be returned without databse
    * if $dbname is "" the database name from config will be used
    * else the $dbname is used and if any instance exist with the name, it will be overwritten
    * To get old stored instance, the $dbname must be ""
    */
    public static function &getModuleInstance($name="default",$dbname="",$dbuser="",$dbpass="", $sqlserver="mysql", $host="localhost")
    {
        return self::getNamedInstance($name,$dbname,$dbuser,$dbpass, $sqlserver, $host);
    }

    /**
    * if $dbname is false, the pdo will be returned without databse
    * if $dbname is "" the database name from config will be used
    * else the $dbname is used and if any instance exist with the name, it will be overwritten
    * To get old stored instance, the $dbname must be ""
    */
    public static function &getNamedInstance($name="default",$dbname="",$dbuser="",$dbpass="", $sqlserver="mysql", $host="localhost")
    {
        static $arrPDOName=array();
        if(!isset($arrPDOName[$name]) && $dbname!=="")
        {
            if(isset(self::$pdo[$dbname]))
            {
                $arrPDOName[$name]=$dbname;
                self::$pdo[$dbname]->name=$name;
                return self::$pdo[$dbname];
            }
        }
        if(isset($arrPDOName[$name]))
        {
            if(isset(self::$pdo[$arrPDOName[$name]]))
            {
                return self::$pdo[$arrPDOName[$name]];
            }
            else
            {
                trace("unexpected PDO instance requested");
            }
        }
        if($dbname==="")
        {
            if($name=="client")
            {
                if(getDynamicDatabase())
                {
                    $dbname=getDynamicDatabase();
                }
                else
                {
                    $dbname=getAppConfig("DATABASE_NAME");
                }
            }
            else
            {
                $dbname=getAppConfig("DATABASE_NAME");
            }
        }
        if(isset(self::$pdo[$dbname]))
        {
                $arrPDOName[$name]=$dbname;
                return self::$pdo[$dbname];
        }
        if($dbuser==="")
        {
            $dbuser=getAppConfig("DATABASE_USER");
        }
        if($dbpass==="")
        {
            $dbpass=getAppConfig("DATABASE_PASSWORD");
        }

        self::$pdo[$dbname]=new ClsNaanalPDO($host,$dbuser,$dbpass,$dbname,$sqlserver);
        self::$pdo[$dbname]->name=$name;
        $arrPDOName[$name]=$dbname;
        self::$instanceCount++;
        return self::$pdo[$dbname];
        /*
        $arrPDOName[$name]=$dbname;
        {
            if($dbuser==="" && $dbpass==="")
            {
                if($dbname==="")
                {
                
                   $dbname=getAppConfig("DATABASE_NAME");
                }
            }
        }
  
        if($name=="default")
        {
            
        }
        if(array_key_exists($name,$arrPDOName))
        {
            $PDODBName=$arrPDOName[$name];
            if($dbname==="" || $dbname==$PDODBName)
            {
                return self::$pdo[$PDODBName];
            }
        }
        if(array_key_exists($name,$arrPDOName)) 
        {
            $PDODBName=$arrPDOName[$name];
            if($dbname!=$PDODBName)
            {
                self::$pdo[$PDODBName]->statement=null;
                unset(self::$pdo[$PDODBName]);
            }
        }
        if(!array_key_exists($dbname,self::$pdo))
        {
            if(self::$instanceCount>=self::$instanceMax) 
            {
                self::$arrStaticError[]="maximum instance reached";trace(self::$arrStaticError);
                return false;
            }
            if($dbname==="naanal_default")
            {
                ///if user also empty, connect to naanal framework's sqlite database
                if(empty($user))
                {
                    $path=  dirname(dirname(__FILE__))."/naanal.sqlite";
                    self::$pdo[$dbname]=new ClsNaanalPDO($host,false,false,$path,"sqlite");
                    self::$pdo[$dbname]->name=$name;
                    $arrPDOName[$name]=$dbname;
                }
                else
                {
                    self::$pdo[$dbname]=new ClsNaanalPDO($host,$user,$pass,null,$sqlserver);
                    self::$pdo[$dbname]->name=$name;
                    $arrPDOName[$name]=$dbname;
                }
            }
            else
            {
                if($dbname==="")
                {
                    $dbname=getAppConfig("DATABASE_NAME");
                }
                if($dbname=="INFORMATION_SCHEMA")
                {
                    $user=$dbuser==""?ClsConfig::$DATABASE[$dbname]["user"]:$dbuser;
                    $pass=$dbpass==""?ClsConfig::$DATABASE[$dbname]["password"]:$dbpass;
                }
                else
                {
                    if(!isset(ClsConfig::$DATABASE[$dbname]))
                    {
                        trace("Unexpected databse");
                    }
                    $user=$dbuser==""?ClsConfig::$DATABASE[$dbname]["user"]:$dbuser;
                    $pass=$dbpass==""?ClsConfig::$DATABASE[$dbname]["password"]:$dbpass;
                }
                self::$pdo[$dbname]=new ClsNaanalPDO($host,$user,$pass,$dbname,$sqlserver);
                self::$pdo[$dbname]->name=$name;
                $arrPDOName[$name]=$dbname;
            }
            self::$instanceCount++;
        }
        return self::$pdo[$arrPDOName[$name]];*/
    }

    function &getQueryObject($class)
    {
        $stmt=$this->query($this->sql);
        if(empty($this->arrParameter))
        {
            $stmt->setFetchMode(PDO::FETCH_CLASS, $class);
            $arrObject=$stmt->fetchAll();
            return $arrObject;
        }
    }
    function queryAndFetchRow($sql,$param=false)
    {
        if($param===false)
            $obj=$this->query($sql);
        else
            $obj=$this->pquery($sql, $param);
        if($obj===false) return false;
        return $this->fetch_assoc();
    }
    function queryAndFetchRowIfSingle($sql,$param=false)
    {
        if($param===false)
            $obj=$this->query($sql);
        else
            $obj=$this->pquery($sql, $param);
        if($obj===false) return false;
        if($this->rowCount>1) return false;
        return $this->fetch_assoc();
    }
    function queryAndFetchRows($sql,$param=false)
    {
        if($param===false)
            $this->query($sql);
        else
            $this->pquery($sql, $param);
        return $this->getAllRow();
    }
    function setQuery($sql,$arrTmpParam=null)
    {
        if(empty($sql))
        {
            $this->arrError[]=array("message"=>"Unexpected empty sql: {$sql}","errorInfo"=>$this->errorInfo(),"errorCode"=>$this->errorCode(),"statementErrorInfo"=>$this->statement->errorInfo(),"statementErrorCode"=>$this->statement->errorCode(),"sql"=>$sql);
            if($this->stopOnError) trace($this->arrError);
            return false;
        }
        $objParam=$arrTmpParam;
        $this->arrLastSql[]=$sql;
        $this->sql=$sql;
        addLog($sql,1);
        try
        {
            if(empty($arrTmpParam)) 
            {        
                $this->statement=$this->query($sql);
                if($this->statement!==false) return true;
                else if(empty($this->statement))
                {
                    trace("PDO query returns unexpected empty instead of expected FALSE or statement object");
                }
                else 
                {
                    $this->arrError[]=array("message"=>"Statement Object Empty","errorInfo"=>$this->errorInfo(),"errorCode"=>$this->errorCode(),"sql"=>$sql);
                    if($this->stopOnError) trace($this->arrError);
                    return false;
                }
            }
            else 
            {
                if(is_object($arrTmpParam)) $arrTmpParam=$arrTmpParam->getArray();
                if(!is_array($arrTmpParam)) 
                {
                    $this->error=new PDOException("Array Expected");
                    $this->arrError[]=array("message"=>"Array Expected for \$arrTmpParam","data"=>$arrTmpParam);
                    if($this->stopOnError) trace($this->arrError);
                    return false;
                }
                $matches=array();
                ///getting all the named placeholders
                preg_match_all("/:[a-zA-Z0-9_]*/", $sql, $matches);
                $arrAttribute=$matches[0];
                ///this variable is used to store the necessary object attribute and filtering remaining
                $arrParam=null;
                foreach($arrAttribute as $ind=>$match)
                {
                    $match=trim($match,":");
                    $arrParam[$match]=$arrTmpParam[$match];
                }    
                $this->statement=$this->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
                //trace($arrParam);
                $this->statement->execute($arrParam);
                return true;
            }
        }
        catch(PDOException $e)
        {trace($e);
            if(empty($this->statement))
            {
                $this->arrError[]=array("message"=>"Exception Occured for Database:({$this->dbname})","errorInfo"=>$this->errorInfo(),"errorCode"=>$this->errorCode(),"sql"=>$sql, "exception"=>$e);
            }
            else
            {
                $this->arrError[]=array("message"=>"Exception Occured for Database:({$this->dbname})","errorInfo"=>$this->errorInfo(),"errorCode"=>$this->errorCode(),"statementErrorInfo"=>$this->statement->errorInfo(),"statementErrorCode"=>$this->statement->errorCode(),"sql"=>$sql, "exception"=>$e);
            }
            if($this->stopOnError) trace($this->arrError);
            return false;
        }				
    }	
    function &getObject($class=false,$arrParam=null)
    {
        if($class===false)
        {
            if($obj=$this->statement->fetchObject()) 
            {
                return $obj; 
            }
        }
        if(!class_exists($class))
        {
            $this->arrError[]=array("message"=>"Unexpected empty statement");
            if($this->stopOnError) trace($this->arrError);
            return false;
        }
        $obj=null;
        if(is_null($this->statement)) 
        {
            $this->arrError[]=array("message"=>"Unexpected empty statement","errorInfo"=>$this->errorInfo(),"errorCode"=>$this->errorCode());
            if($this->stopOnError) trace($this->arrError);
            $obj=false;
            return $obj;
        }
        try
        {   
            if(is_null($arrParam))
            {
                if($obj=$this->statement->fetchObject($class)) 
                {
                    return $obj; 
                }
            }
            else 
            {
                if(empty($this->statement))
                {
                    $this->arrError[]=array("message"=>"Unexpected empty statement","errorInfo"=>$this->errorInfo(),"errorCode"=>$this->errorCode());
                    if($this->stopOnError) trace($this->arrError);
                    return false;
                }
                $obj=$this->statement->fetchObject($class,$arrParam);
                if(empty($this->statement))
                {
                    $this->arrError[]=array("message"=>"Fetching object throws error and statement object empty for class: {$class} and parameters:".print_r($arrParam,true).". Verify the class {$class} is valid","errorInfo"=>$this->errorInfo(),"errorCode"=>$this->errorCode());
                    if($this->stopOnError) trace($this->arrError);
                    return false;
                }
                return $obj; 
            }
            return $obj;
        }
        catch(PDOException $e)
        {
            $this->arrError[]=array("message"=>"Exception Occured","errorInfo"=>$this->errorInfo(),"errorCode"=>$this->errorCode(),"statementErrorInfo"=>$this->statement->errorInfo(),"statementErrorCode"=>$this->statement->errorCode());
            if($this->stopOnError) trace($this->arrError);
            return false;
        }						
    } 	
    function &getAssoc()
    {
        try
        {
            if($row=$this->statement->fetch(PDO::FETCH_ASSOC))
            {
                return $row;
            }
            $row=null;
            return $row;
        }
        catch(PDOException $e)
        {
            $this->error=$e;
            $this->arrError[]=array("message"=>"Exception Occured","errorInfo"=>$this->errorInfo(),"errorCode"=>$this->errorCode(),"statementErrorInfo"=>$this->statement->errorInfo(),"statementErrorCode"=>$this->statement->errorCode());
            $row=false;
            if($this->stopOnError) trace($this->arrError);
            return $row;
        }	
    }
    public function getColumnCount()
    {
        return $this->statement->columnCount();
    }
    public function getRowCount()
    {
        return $this->statement->rowCount();
    }
    function getColumn($colname,$isKeyID=false)
    {       
        if(empty($this->statement))
        {
            $this->arrError[]=array("message"=>"Statement Object Empty at getColumn method. Param:colname={$colname}, isKeyID={$isKeyID}");
            if($this->stopOnError) trace($this->arrError);
            return false;
        }
        try
        {
            $this->statement->setFetchMode(PDO::FETCH_ASSOC);
            if($arr=$this->statement->fetchALL()) 
            {
                $arrCol=array();
                foreach($arr as $ind=>$r)
                {
                    if($isKeyID)
                    {
                        $arrCol[$r["id"]]=$r[$colname];
                    }
                    else
                        $arrCol[]=$r[$colname];
                } 
                return $arrCol;
            }
            return null;
        }
        catch(PDOException $e)
        {
            $this->error=$e;
            $this->arrError[]=array("message"=>"Exception Occured","errorInfo"=>$this->errorInfo(),"errorCode"=>$this->errorCode(),"statementErrorInfo"=>$this->statement->errorInfo(),"statementErrorCode"=>$this->statement->errorCode());
            if($this->stopOnError) trace($this->arrError);
            return false;
        }
    }
    public function getError()
    {
        return $this->arrError;
    }
    function &getAllObject($class=false,$arrParam=null)
    {
        if($class===false)
        {
            $arrObj=array();
            while($obj=$this->getObject())
            {
                $arrObj[]=$obj;
            }
        }
        if(!class_exists($class))
        {
            $this->arrError[]=array("message"=>"{$class} not exist");
            if($this->stopOnError) trace($this->arrError);
            return false;
        }
        $arrObj=array();
        while($obj=$this->getObject($class,$arrParam))
        {
            $arrObj[]=$obj;
        }
        if($this->arrError)
        {
            $arrObj=false;
            if($this->stopOnError) trace($this->arrError);
            return $arrObj;
        }
        return $arrObj;
    }
    function getAllAssoc()
    {       
        if(empty($this->statement))
        {
            $this->arrError[]=array("message"=>"Statement Object Empty","errorInfo"=>$this->errorInfo(),"errorCode"=>$this->errorCode());
            if($this->stopOnError) trace($this->arrError);
            return false;
        }
        try
        {
            $this->statement->setFetchMode(PDO::FETCH_ASSOC);
            if($arr=$this->statement->fetchALL()) 
            {
                return $arr;
            }
            return null;
        }
        catch(PDOException $e)
        {
            $this->error=$e;
            if($this->stopOnError) trace($this->arrError);
            return false;
        }
    }
    function getAllRow()
    {       
        try
        {
            if(empty($this->statement))
            {
                $this->arrError[]="Unexpected statement object empty";
                if($this->stopOnError) trace($this->arrError);
                return false;
            }
            $this->statement->setFetchMode(PDO::FETCH_NUM);
            if($arr=$this->statement->fetchALL()) 
            {
                return $arr;
            }
            return null;
        }
        catch(PDOException $e)
        {
            $this->error=$e;
            if($this->stopOnError) trace($this->arrError);
            return false;
        }
    }
    function getAllRowAsTable()
    {       
        try
        {
            $this->statement->setFetchMode(PDO::FETCH_NUM);
            if($arr=$this->statement->fetchALL()) 
            {
                return $arr;
            }
            return null;
        }
        catch(PDOException $e)
        {
            $this->error=$e;
            if($this->stopOnError) trace($this->arrError);
            return false;
        }
    }
    function isTableExist($tablename,$database=null)
    {
        $database=is_null($database)?$this->dbname:$database;
        if(is_null($database)) trace("Database not set");

        $sql="SELECT COUNT(*) as tot FROM information_schema.tables WHERE table_schema = '{$database}' AND table_name = '{$tablename}';";
        $this->setQuery($sql);
        $arrRow = $this->getAllRow();
        if(isset($arrRow[0][0]) && $arrRow[0][0]>0) return true;
        return false;
    }
    function createTable($tablename,$database=null)
    {
        $database=is_null($database)?$this->dbname:$database;
        if(is_null($database)) die("Database not set");
        $sql="CREATE TABLE {$database}.{$tablename} (id INT NOT NULL AUTO_INCREMENT, primary key(id))ENGINE=InnoDB;";
        $this->exec($sql);
    }
    function isFieldExist($tablename,$fieldname,$database=null)
    {
        $database=is_null($database)?$this->dbname:$database;
        if(is_null($database)) die("Database not set");

        $sql="SELECT count(*) AS tot FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='{$database}' AND TABLE_NAME = '{$tablename}' AND COLUMN_NAME = '{$fieldname}'";
        $this->setQuery($sql);
        $arrRow = $this->getAllRow();
        if(isset($arrRow[0][0]) && $arrRow[0][0]>0) return true;
        return false;
    }
    function addField($tablename,$fieldname,$fieldtype,$size=null,$defaultvalue=null,$after=null,$database=null) 
    {
        $fieldtype=trim($fieldtype);
        $fieldtype=strtoupper($fieldtype);
        if($fieldtype=="VARCHAR")
        {
            if(is_null($size))
            {
                $size="255";
            }
        }
        else if($fieldtype=="INT")
        {
            if(is_null($size))
            {
                $size="14";
            }
        }
        $sql="ALTER TABLE {$database}.{$tablename} ADD {$fieldname} {$fieldtype}({$size})";
        if(!is_null($after))
        {
            $sql=$sql." ".$after;
        }
        $this->setQuery($sql);
    }
}
class ClsNaanalDB extends ClsNaanalPDO
{
    function __construct($host,$user,$pass,$dbname,$sqlserver="mysql")
    {
        parent::__construct($host,$user,$pass,$dbname,$sqlserver);
    }
    
}
class ClsNaanalRecords
{
    private $sql="";
    private $arrRecord=array();
    private $arrRecordOriginal=array();
    private $arrNewRecordIndex=array();
    private $arrUpdateRecordIndex=array();
    private $recordCount=-1;
    private $columnCount=-1;
    private $objPDO=null;
    private $arrHead=array();
    private $cellHook=null;
    private $fieldHook=null;
    
    public function __construct($sql,&$objPDO)
    {
        $this->sql=$sql;
        $this->objPDO=$objPDO;
        $this->objPDO->setQuery($sql);
        $this->arrRecord = $this->objPDO->getAllAssoc();
        $this->arrHead=array_keys($this->arrRecord[0]);
        $this->arrRecordOriginal=$this->arrRecord;
        $this->recordCount=count($this->arrRecord);
        $this->columnCount=count($this->arrHead);
    }
    public static function &getInstance($sql,&$objPDO)
    {
        $obj=new ClsNaanalRecords($sql,$objPDO);
        return $obj;
    }
    public function setHook($name,$loopCell)
    {
        $this->$name=$loopCell;
    }
    public function findRecordIndex($arrUniqueRecordData)
    {
        if($this->arrRecord)
        foreach($this->arrRecord as $ind=>$row)
        {
            $isMatch=true;
            foreach($this->arrUniqueRecordField as $field)
            {
                $fieldIndex=array_search($field, $this->arrField);
                if($row[$fieldIndex]!=$arrUniqueRecordData[$field])
                {
                    $isMatch=false;
                    break;
                }
            }
            if($isMatch) return $ind;
        }
        return -1;
    }
    
    public function setData($arrUniqueRecordData,$columnField,$data)
    {
        $index=$this->findRecordIndex($arrUniqueRecordData);
        if($index>-1)
        {
            $this->arrRecord[$index][$columnField]=$data;
            if(!in_array($needle, $this->arrUpdateRecordIndex))
            {
                $this->arrUpdateRecordIndex[]=$index;
            }
            return true;
        }
        return false;
    }
    
    public function insertEmptyRecord()
    {
        $this->arrRecord[$this->recordCount]=$this->emptyRecord;
        $this->arrNewRecordIndex[]=$this->recordCount;
        return $this->recordCount;
    }
    
    public function addRecord($arrField)
    {
        $index=$this->insertEmptyRecord();
        $this->recordCount=$this->recordCount+1;
        foreach($this->arrRecord[$index] as $fieldIndex=>$data)
        {
            $this->arrRecord[$index][$fieldIndex]=$arrField[$this->arrField[$fieldIndex]];
        }
        return true;
    }
    
    public function modifyRecord($arrField)
    {
        $arrUniqueRecordData=array();
        foreach($this->arrUniqueRecordField as $field)
        {
            $arrUniqueRecordData[$field]=$arrField[$field];
        }
        $index=$this->findRecordIndex($arrUniqueRecordData);
        if($index>-1)
        {
            foreach($this->arrRecord[$index] as $fieldIndex=>$data)
            {
                $this->arrRecord[$index][$fieldIndex]=$arrField[$this->arrField[$fieldIndex]];
            }
            $this->arrUpdateRecordIndex[]=$index;
            return true;
        }
        return false;
    }
    
    public function updateRecord($arrField)
    {
        if(!$this->modifyRecord($arrField))
        {
            if(!$this->addRecord($arrField))
            {
                return false;
            }
        }
        return true;
    }
    
    public function setDataByIndex($index,$field,$data)
    {
        $this->arrRecord[$index][$field]=$data;
        if(!in_array($index, $this->arrNewRecordIndex))
        {
            if(!in_array($needle, $this->arrUpdateRecordIndex))
            {
                $this->arrUpdateRecordIndex[]=$index;
            }
        }
    }
    
    public function getDataByIndex($index,$field)
    {
        if(isset($this->arrRecord[$index][$field]))
        {
            return $this->arrRecord[$index][$field];
        }
        return null;
    }
    /**
     * 
     * @param type $row integer
     * @param type $col integer or string
     * @return data or null
     */
    public function getData($row,$col)
    {
        $head=$col;
        if(is_numeric($col))
        {
            $head=$this->arrHead[$col];
        }
        if(isset($this->arrRecord[$row][$head]))
        {
            if(is_null($this->cellHook))
            {
                return $this->arrRecord[$row][$head];
            }
            else
            {
                $lamda=$this->cellHook;
                $data=$lamda($head,$this->arrRecord[$row][$head]);
                return $data;
            }
        }
        return null;
    }
    
    public function getHead($col)
    {
        if(is_null($this->fieldHook))
        {
            return $this->arrHead[$col];
        }
        else
        {
            $lamda=$this->fieldHook;
            $data=$lamda($this->arrHead[$col]);
            return $data;
        }
    }
    
    public function getRecordCount()
    {
        return $this->recordCount;
    }
    
    public function getColumnCount()
    {
        return $this->columnCount;
    }
}
?>